#include "mcl/builtin.h"
#include "mcl/da.h"
#include "mcl/sym.h"
#include "mcl/val.h"
#include "mcl/writer.h"

static struct lvalue VAL_NIL = {.type = MC_NIL};

static void rt_panic(struct runtime *rt)
{
	rt->panic = true;
}

static struct lvalue *builtin_assert(struct runtime *rt, struct env *env)
{
	struct lvalue *cond = env_find(env, &SYM.cond);

	if (cond->type != MC_BOOL) {
		struct lvalue *v = new_val(rt, MC_ERROR);

		struct writer w = new_writer_from_buffer();
		write_cstr(&w, "condition of assert is not boolean: ");
		write_cstr(&w, VTYPE_INFO.name[cond->type]);

		v->via.err.message = w.via.buf;
		return v;
	}

	if (!cond->via.bool_) {
		rt_panic(rt);
		return &VAL_NIL;
	}

	return &VAL_NIL;
}

static struct lvalue *err_type_error(
	char const *name,
	struct runtime *rt,
	enum vtype expect,
	enum vtype ty)
{
	struct lvalue *v = new_val(rt, MC_ERROR);

	struct writer w = new_writer_from_buffer();
	write_cstr(&w, name);
	write_cstr(&w, " is not ");
	write_cstr(&w, VTYPE_INFO.name[expect]);
	write_cstr(&w, ": ");
	write_cstr(&w, VTYPE_INFO.name[ty]);

	v->via.err.message = w.via.buf;
	return v;
}

static struct lvalue *builtin_add(struct runtime *rt, struct env *env)
{
	struct lvalue *lval = env_find(env, &SYM.lhs);
	struct lvalue *rval = env_find(env, &SYM.rhs);

	if (lval->type != MC_I64) {
		return err_type_error("lhs", rt, MC_I64, lval->type);
	}

	if (rval->type != MC_I64) {
		return err_type_error("rhs", rt, MC_I64, rval->type);
	}

	struct lvalue *r = new_val(rt, MC_I64);
	r->via.i64 = lval->via.i64 + rval->via.i64;

	return r;
}

struct lvalue *builtin_sub(struct runtime *rt, struct env *env)
{
	struct lvalue *lval = env_find(env, &SYM.lhs);
	struct lvalue *rval = env_find(env, &SYM.rhs);

	if (lval->type != MC_I64) {
		return err_type_error("lhs", rt, MC_I64, lval->type);
	}

	if (rval->type != MC_I64) {
		return err_type_error("rhs", rt, MC_I64, rval->type);
	}

	struct lvalue *r = new_val(rt, MC_I64);
	r->via.i64 = lval->via.i64 - rval->via.i64;

	return r;
}

static struct lvalue *builtin_mul(struct runtime *rt, struct env *env)
{
	struct lvalue *lval = env_find(env, &SYM.lhs);
	struct lvalue *rval = env_find(env, &SYM.rhs);

	if (lval->type != MC_I64) {
		return err_type_error("lhs", rt, MC_I64, lval->type);
	}

	if (rval->type != MC_I64) {
		return err_type_error("rhs", rt, MC_I64, rval->type);
	}

	struct lvalue *r = new_val(rt, MC_I64);
	r->via.i64 = lval->via.i64 * rval->via.i64;

	return r;
}

static struct lvalue *builtin_div(struct runtime *rt, struct env *env)
{
	struct lvalue *lval = env_find(env, &SYM.lhs);
	struct lvalue *rval = env_find(env, &SYM.rhs);

	if (lval->type != MC_I64) {
		return err_type_error("lhs", rt, MC_I64, lval->type);
	}

	if (rval->type != MC_I64) {
		return err_type_error("rhs", rt, MC_I64, rval->type);
	}

	struct lvalue *r = new_val(rt, MC_I64);
	r->via.i64 = lval->via.i64 / rval->via.i64;

	return r;
}

static struct lvalue *builtin_head(struct runtime *rt, struct env *env)
{
	struct lvalue *ls_val = env_find(env, &SYM.ls);

	if (ls_val->type != MC_LIST) {
		return err_type_error("ls", rt, MC_LIST, ls_val->type);
	}

	if (ls_val->via.list.len == 0) {
		struct lvalue *v = new_val(rt, MC_ERROR);

		struct writer w = new_writer_from_buffer();
		write_cstr(&w, "head: list is empty");

		v->via.err.message = w.via.buf;
		return v;
	}

	return &ls_val->via.list.data[0];
}

static struct lvalue *builtin_tail(struct runtime *rt, struct env *env)
{
	struct lvalue *ls_val = env_find(env, &SYM.ls);

	if (ls_val->type != MC_LIST) {
		return err_type_error("ls", rt, MC_LIST, ls_val->type);
	}

	if (ls_val->via.list.len == 0) {
		return ls_val;
	}

	struct lvalue *v = new_val(rt, MC_LIST);
	v->via.list = slice_tail(&ls_val->via.list);
	return v;
}

// static struct val *builtin_list(struct runtime *rt, struct env *env)
// {
// }

void rt_add_builtin(struct env *env)
{
#define BIND_BUILTIN(NAME) \
	env_bind(env, SYM.NAME, new_builtin(env, builtin_##NAME, args))

	{
		// FUNC(lhs, rhs)
		struct da_dstr args = DA_INIT;

		DA_APPEND(args, SYM.lhs);
		DA_APPEND(args, SYM.rhs);

		BIND_BUILTIN(assert);
		BIND_BUILTIN(add);
		BIND_BUILTIN(sub);
		BIND_BUILTIN(mul);
		BIND_BUILTIN(div);
	}

	{
		// FUNC(ls)
		struct da_dstr args = DA_INIT;

		DA_APPEND(args, SYM.ls);

		BIND_BUILTIN(head);
		BIND_BUILTIN(tail);
	}
}
